package com.czg.jsonschema2lombokpojo;

import static org.apache.commons.lang3.StringUtils.*;

import java.awt.*;
import java.io.*;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import cn.hutool.core.io.FileUtil;
import com.czg.jsonschema2lombokpojo.annotator.Annotator;
import com.czg.jsonschema2lombokpojo.annotator.AnnotatorFactory;
import com.czg.jsonschema2lombokpojo.annotator.Jackson2Annotator;
import com.czg.jsonschema2lombokpojo.config.DefaultGenerationConfig;
import com.czg.jsonschema2lombokpojo.config.GenerationConfig;
import com.czg.jsonschema2lombokpojo.exception.GenerationException;
import com.czg.jsonschema2lombokpojo.rules.RuleFactory;
import com.czg.jsonschema2lombokpojo.util.NameHelper;
import com.czg.jsonschema2lombokpojo.util.URLUtil;
import org.apache.commons.io.FilenameUtils;


import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.sun.codemodel.CodeWriter;
import com.sun.codemodel.JCodeModel;

public class JsonSchema2LombokPojo {

    private static FileFilter _fileFilter;

    /**
     * Reads the contents of the given source and initiates schema generation.
     *
     * @param config the configuration options (including source and target paths,
     *               and other behavioural options) that will control code
     *               generation
     * @throws FileNotFoundException if the source path is not found
     * @throws IOException           if the application is unable to read data from the source
     */
    public static void generate(GenerationConfig config, Iterator<URL> allSource, FileFilter fileFilter,
                                boolean isRemoveOldOutput,
                                File outPutTargetDirectory,
                                String targetPackage, String outJavaSourceCodeEncoding,
                                SourceSortOrder sourceSortOrder, RuleFactory ruleFactory) throws IOException {
        _fileFilter = fileFilter;
        JCodeModel codeModel = new JCodeModel();
        SchemaMapper mapper = new SchemaMapper(ruleFactory, createSchemaGenerator(config));
        if (isRemoveOldOutput) {
            removeOldOutput(outPutTargetDirectory);
        }


        for (Iterator<URL> sources = allSource; sources.hasNext(); ) {
            URL source = sources.next();
            if (URLUtil.parseProtocol(source.toString()) == URLProtocol.FILE && URLUtil.getFileFromURL(source).isDirectory()) {
                generateRecursive(config, mapper, codeModel, defaultString(targetPackage), Arrays.asList(URLUtil.getFileFromURL(source).listFiles(_fileFilter)));
            } else {
                mapper.generate(codeModel, getNodeName(source, config), defaultString(targetPackage), source);
            }
        }

        if (outPutTargetDirectory.exists() || outPutTargetDirectory.mkdirs()) {
            CodeWriter sourcesWriter = new FileCodeWriterWithEncoding(outPutTargetDirectory, outJavaSourceCodeEncoding);
            CodeWriter resourcesWriter = new FileCodeWriterWithEncoding(outPutTargetDirectory, outJavaSourceCodeEncoding);
            codeModel.build(sourcesWriter, resourcesWriter);
        } else {
            throw new GenerationException("Could not create or access target directory " + outPutTargetDirectory.getAbsolutePath());
        }
    }

    public static void generate(GenerationConfig config, URL source,
                                boolean isRemoveOldOutput,
                                File outPutTargetDirectory,
                                String targetPackage, String outJavaSourceCodeEncoding) throws IOException {
        RuleFactory ruleFactory = new RuleFactory(config, new Jackson2Annotator(config), new SchemaStore());
        JCodeModel codeModel = new JCodeModel();
        SchemaMapper mapper = new SchemaMapper(ruleFactory, createSchemaGenerator(config));
        if (isRemoveOldOutput) {
            removeOldOutput(outPutTargetDirectory);
        }
        mapper.generate(codeModel, getNodeName(source, config), defaultString(targetPackage), source);

        if (outPutTargetDirectory.exists() || outPutTargetDirectory.mkdirs()) {
            CodeWriter sourcesWriter = new FileCodeWriterWithEncoding(outPutTargetDirectory, outJavaSourceCodeEncoding);
            CodeWriter resourcesWriter = new FileCodeWriterWithEncoding(outPutTargetDirectory, outJavaSourceCodeEncoding);
            codeModel.build(sourcesWriter, resourcesWriter);
        } else {
            throw new GenerationException("Could not create or access target directory " + outPutTargetDirectory.getAbsolutePath());
        }
    }

    private static ContentResolver createContentResolver(GenerationConfig config) {
        if (config.getSourceType() == SourceType.YAMLSCHEMA || config.getSourceType() == SourceType.YAML) {
            return new ContentResolver(new YAMLFactory());
        } else {
            return new ContentResolver();
        }
    }

    private static SchemaGenerator createSchemaGenerator(GenerationConfig config) {
        if (config.getSourceType() == SourceType.YAMLSCHEMA || config.getSourceType() == SourceType.YAML) {
            return new SchemaGenerator(new YAMLFactory());
        } else {
            return new SchemaGenerator();
        }
    }


    private static void generateRecursive(GenerationConfig config, SchemaMapper mapper, JCodeModel codeModel, String packageName, List<File> schemaFiles) throws IOException {

        Collections.sort(schemaFiles, SourceSortOrder.SUBDIRS_FIRST.getComparator());

        for (File child : schemaFiles) {
            if (child.isFile()) {
                if (config.getSourceType() == SourceType.JSON || config.getSourceType() == SourceType.YAML) {
                    //任何缓存的模式都将具有相对于上一个文档而言的片段式的 ID（并且不应被重复使用）
                    // any cached schemas will have ids that are fragments, relative to the previous document (and shouldn't be reused)
                    mapper.getRuleFactory().getSchemaStore().clearCache();
                }
                mapper.generate(codeModel, getNodeName(child.toURI().toURL(), config), defaultString(packageName), child.toURI().toURL());
            } else {
                generateRecursive(config, mapper, codeModel, childQualifiedName(packageName, child.getName()), Arrays.asList(child.listFiles(_fileFilter)));
            }
        }
    }

    private static String childQualifiedName(String parentQualifiedName, String childSimpleName) {
        String safeChildName = childSimpleName.replaceAll(NameHelper.ILLEGAL_CHARACTER_REGEX, "_");
        return isEmpty(parentQualifiedName) ? safeChildName : parentQualifiedName + "." + safeChildName;
    }

    private static void removeOldOutput(File targetDirectory) {
        if (targetDirectory.exists()) {
            for (File f : targetDirectory.listFiles()) {
                delete(f);
            }
        }
    }

    @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "RV_RETURN_VALUE_IGNORED_BAD_PRACTICE")
    private static void delete(File f) {
        if (f.isDirectory()) {
            for (File child : f.listFiles()) {
                delete(child);
            }
        }
        f.delete();
    }


    public static String getNodeName(URL file, GenerationConfig config) {
        return getNodeName(file.toString(), config);
    }

    public static String getNodeName(String filePath, GenerationConfig config) {
        try {
            String fileName = FilenameUtils.getName(URLDecoder.decode(filePath, StandardCharsets.UTF_8.toString()));
            String[] extensions = config.getFileExtensions() == null ? new String[]{} : config.getFileExtensions();

            boolean extensionRemoved = false;
            for (String s : extensions) {
                String extension = s;
                if (extension.length() == 0) {
                    continue;
                }
                if (!extension.startsWith(".")) {
                    extension = "." + extension;
                }
                if (fileName.endsWith(extension)) {
                    fileName = removeEnd(fileName, extension);
                    extensionRemoved = true;
                    break;
                }
            }
            if (!extensionRemoved) {
                fileName = FilenameUtils.getBaseName(fileName);
            }
            return fileName;
        } catch (UnsupportedEncodingException e) {
            throw new IllegalArgumentException(String.format("Unable to generate node name from URL: %s", filePath), e);
        }
    }

    public static void main(String[] args) throws IOException {
        DefaultGenerationConfig config = new DefaultGenerationConfig();
        URL source = URLUtil.fileUrl("D:/JsonSchema2Entity/src/main/resources/table-schema.json");
        File outPutTargetDirectory = new File("c:\\aaaa\\");
        JsonSchema2LombokPojo.generate(config, source, true,
                outPutTargetDirectory, "com.czg.entity", StandardCharsets.UTF_8.toString());
        Desktop.getDesktop().open(outPutTargetDirectory);
    }
}
